package bexpred;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

/*
 * BExpred - Boolean Expression Reducer
 * Goal: To reduce specified boolean expressions
 * Current state: Evaluates a given expression with input values
 * Copyright (c) 2003 Benjamin Biron
 * License: GPL

    This file is part of BExpred.

    BExpred is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    BExpred is distributed in the hope that it will be useful,rs
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with BExpred; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 */

/**
 * @author Tomasz Werszko
 * @author Rafal Magda
 */

public class BExprTree {
  private String expression; // The expression representing the expression tree
  public String orig_expression; // Contains the original expression as entered by the user
  private int var_count; // The amount of unique variables
  private ArrayList<String> vars; // The list of variables, strings.
  private boolean case_sensitive = false; // Is the expression case sensitive?
  private BExprNode root; // The root node.
  private TruthTable truth_table;

  public BExprTree() {
    //This shouldn't be called, but we'll leave it in case it becomes handy
    this.var_count = 0;
  }

  public BExprTree(String expression) throws BExprPreParseException {
    this.setExpression(expression);
  }

  public BExprTree(String expression, boolean case_sensitive) throws BExprPreParseException {
    this.case_sensitive = case_sensitive;
    this.setExpression(expression);
  }

  public TruthTable getTruthTable() {
    return this.truth_table;
  }

  public void setExpression(String expression) throws BExprPreParseException {
    //DEBUG System.out.println(expression);
    BExprPreParser preParser;
    this.orig_expression = expression;
    this.expression = expression.trim();

    try {
      //DEBUG System.out.println(this.expression);
      preParser = new BExprPreParser(this.expression);
      this.expression = preParser.getExpression();
      this.var_count = this.updateVars();
      this.root = new BExprNode(this.expression);
      this.root.setInverted(preParser.isInverted() || this.root.isInverted());
      this.truth_table = new TruthTable(this);
    } catch (BExprPreParseException e) {
      System.out.println(e);
      this.vars = new ArrayList<String>();
      this.var_count = 0;
      throw e;
    }
  }

  public ArrayList<String> getVars() {
    // Basic accessor method
    return this.vars;
  }

  public int getVarCount() {
    return this.var_count;
  }

  /**
   * This method simply sets the variables List
   * @return the amount of unique variables taking into account case if desired
   */
  private int updateVars() {
    // This method simply sets the vars List and returns the amount of unique
    // variables taking into account case if desired.
    //
    // These are almost all state variables
    char aChar;
    String aVar = "";
    ArrayList<String> boolVars = new ArrayList<String>();
    boolean inVar = false;
    boolean isVar = true;
    boolean varExists = false;
    if (!this.case_sensitive) {
      this.expression = this.expression.toUpperCase();
    }

    this.expression += " "; // Pad the expression to catch the last "value"
    for (int i = 0; i < this.expression.length(); i++) {
      aChar = this.expression.charAt(i);
      isVar = BExprParser.isVarChar(aChar);
      if (!inVar && isVar) {
        inVar = true;
        aVar += aChar;
      } else if (inVar) {
        if (!isVar) {
          inVar = false;
          for (int s = 0; s < boolVars.size(); s++) {
            if (aVar.compareTo((String)boolVars.get(s)) == 0)
              varExists = true;
          }

          if (!varExists)
            boolVars.add(aVar);
          else
            varExists = false;

          aVar = "";
        } else {
          aVar += aChar;
        }
      }
    }

    Object[] a;
    a = boolVars.toArray();
    Arrays.sort(a);
    List<Object> temp_list = Arrays.asList(a);
    
    this.vars = new ArrayList<String>();
    Iterator<Object> iterator = temp_list.iterator();
    while(iterator.hasNext()){
    	Object element = iterator.next();
    	this.vars.add((String)element);
    }
    //this.vars = new ArrayList(Arrays.asList(a));
    return this.vars.size();
  }

  /**
   * This is the evaluate method that should be called (should work with calling it on root directly, but that may change) 
   * @param values
   * @return boolean
   */
  public boolean evaluate(Hashtable<String, String> values) {
    // This is the evaluate method that should be called (should work with calling it on root directly, but that may change)
    return this.root.evaluate(values);
  }

  public boolean evaluate(boolean values[]) {
    Hashtable<String, String> h_values = new Hashtable<String, String>(this.var_count);

    for (int i = 0; i < this.var_count; i++) {
      h_values.put(this.vars.get(i), values[i] ? "1" : "0");
    }
    return this.evaluate(h_values);
  }
  
  /**
   * Reduces expression to SOP
   * @return String
   */
  public String SOPReducerToString(){
	  return this.getTruthTable().getSOP(this.getVars());
  }
 
  /**
   * Reduces expression to POS
   * @return String
   */
  public String POSReducerToString(){
	  return this.getTruthTable().getPOS(this.getVars());
  }

  /**
   * Reduces expression to SOP
   * @return BExprTree
   * @throws BExprPreParseException 
   */
  public BExprTree SOPReducer() throws BExprPreParseException{
	  String sop = this.getTruthTable().getSOP(this.getVars());
	  return new BExprTree(sop);
  }
 
  /**
   * Reduces expression to POS
   * @return BExprTree
   * @throws BExprPreParseException 
   */
  public BExprTree POSReducer() throws BExprPreParseException{
	  String pos = this.getTruthTable().getPOS(this.getVars());
	  return new BExprTree(pos);
  }  
  
  /**
   * Compares two expressions by comparing variable names and truth tables.
   * @param aTree second expression
   * @return true if expressions are equal
   */  
  public boolean compareExprTo(BExprTree aTree) {
	  //reducing expressions
	  BExprTree thisExpr = null;
	  BExprTree aTreeExpr = null;
	  
	  String thisReduced = this.getTruthTable().getSOP(this.getVars());
	  String aTreeReduced = aTree.getTruthTable().getSOP(aTree.getVars());
	  
	  try {
	    	thisExpr = new BExprTree(thisReduced);
	    	aTreeExpr = new BExprTree(aTreeReduced);
	    } catch (BExprPreParseException ex) {
	    	thisExpr = null;
	    	aTreeExpr = null;
	    	ex.printStackTrace();
	    }
	    
	    //comparing two expressions
	    if (thisExpr.getVarCount() != aTreeExpr.getVarCount())
	    	return false;
	    else
	    	return thisExpr.getVars().equals(aTreeExpr.getVars()) && thisExpr.getTruthTable().equals(aTreeExpr.getTruthTable());
	}
  
  
  /**
   * Compares two expressions by comparing truth tables.
   * @param aTree second expression
   * @return true if expressions are equal
   */  
  public boolean compareTo(BExprTree aTree) {
	  if (this.getVarCount() != aTree.getVarCount())
	    	return false;
	    else{
	    	return this.getVars().equals(aTree.getVars()) && this.getTruthTable().equals(aTree.getTruthTable());
	  	}
  }

  /**
   * Invert the current expression by converting ORs to ANDs, and ANDs to ORs,
   * will fail if the tree contains operators other than ORs and ANDs. It also
   * inverts and input variables.
   * @throws Exception
   */
  public void invert() throws Exception {
    this.root.invert();
  }

  public String toString() {
    return this.root.toString();
  }
}